package models

import (
	"errors"
	"strings"
	"time"

	"gitee.com/sansaniot/ssiot-core/facade/env"
	"gitee.com/sansaniot/ssiot-core/httpmvc/model"
	"github.com/henrylee2cn/ameda"
	"gorm.io/datatypes"
	"gorm.io/gorm"

	"ssdevice/common/constant"
	cDto "ssdevice/common/dto"
	"ssdevice/common/enums"
	"ssdevice/common/middleware/mqtt"
	"ssdevice/common/utils"
	"ssdevice/internal/device/models/query"
)

type SysDev struct {
	Id           string         `json:"id" gorm:"primaryKey;type:varchar(255);comment:id"`
	Sn           string         `json:"sn" gorm:"type:varchar(255);comment:设备序列号"`
	Name         string         `json:"name" gorm:"type:varchar(255);comment:设备名称"`
	DeptId       string         `json:"deptId" gorm:"type:varchar(255);comment:分组id"`
	OnlineStatus string         `json:"onlineStatus" gorm:"type:varchar(255);comment:在线状态"`
	Type         string         `json:"type" gorm:"type:varchar(255);comment:类型"`
	ProductModel string         `json:"productModel" gorm:"type:varchar(255);comment:产品编码"`
	GatewaySn    string         `json:"gatewaySn" gorm:"type:varchar(255);comment:网关SN"`
	AlarmStatus  bool           `json:"alarmStatus" gorm:"type:bool;comment:是否告警"`
	Location     datatypes.JSON `json:"location" gorm:"type:jsonb;comment:地址经纬度"`
	View         datatypes.JSON `json:"view" gorm:"type:jsonb;comment:个性页"`

	CreateTime   *cDto.LocalTime `json:"createTime" gorm:"type:timestamptz(6);comment:创建时间"`
	UpdateTime   *cDto.LocalTime `json:"updateTime" gorm:"type:timestamptz(6);comment:更新时间"`
	LastLiveTime *cDto.LocalTime `json:"lastLiveTime" gorm:"type:timestamptz(6);comment:末次交互时间"`
	RegisterTime *cDto.LocalTime `json:"registerTime" gorm:"type:timestamptz(6);comment:注册时间"`
	LogoutTime   *cDto.LocalTime `json:"logoutTime" gorm:"type:timestamptz(6);comment:登出时间"`

	Protocol string `json:"protocol" gorm:"type:varchar(255);comment:设备协议"`

	model.Model

	DeptName     string                 `json:"deptName" gorm:"-"`
	SvcInfo      map[string]interface{} `json:"svcInfo" gorm:"-"`
	ProtocolName string                 `json:"protocolName" gorm:"-"`
}

func (SysDev) TableName() string {
	return "sys_dev"
}

func (e *SysDev) Generate() interface{} {
	o := *e
	return &o
}

func (e *SysDev) GetId() interface{} {
	return e.Id
}

func (e *SysDev) FindDeviceList(req *query.SysDevQuery) (err error, result interface{}) {
	list := make([]SysDev, 0)
	var count int64
	if req.PageIndex > 0 && req.PageSize > 0 {
		err = e.FindPage(&list, &count,
			cDto.MakeCondition(req.GetNeedSearch()),
			cDto.OrderDest("create_time", true, req.OrderBy),
			cDto.Paginate(req.GetPageSize(), req.GetPageIndex()),
		)
		groupIdName, _ := utils.GroupIdName(e.Orm)
		protocolMap := (&SysProtocol{}).FindProtocolMap(e.Orm)
		for i := range list {
			if len(list[i].DeptId) > 0 {
				if deptName, ok := groupIdName[list[i].DeptId]; ok {
					list[i].DeptName = deptName
				}
			}
			if timeI, ok := constant.DevOnlineCache.Get(list[i].Sn); ok {
				list[i].OnlineStatus = enums.Online
				timeTime, _ := time.ParseInLocation("2006-01-02 15:04:05", timeI, time.Local)
				t := cDto.LocalTime(timeTime)
				list[i].LastLiveTime = &t
			} else {
				list[i].OnlineStatus = enums.Offline
			}
			if len(list[i].Protocol) > 0 {
				if sysprotocol, ok := protocolMap[list[i].Protocol]; ok {
					list[i].ProtocolName = sysprotocol.Label
				}
			}
		}
		result = &cDto.Page{
			Records:   list,
			Total:     count,
			PageSize:  req.GetPageSize(),
			PageIndex: req.GetPageIndex(),
			Pages:     (count + int64(req.GetPageSize()) - 1) / int64(req.GetPageSize()),
			Other:     nil,
		}
	} else {
		err = e.FindList(&list,
			cDto.MakeCondition(req.GetNeedSearch()),
			cDto.OrderDest("create_time", true, req.OrderBy),
		)
		groupIdName, _ := utils.GroupIdName(e.Orm)
		protocolMap := (&SysProtocol{}).FindProtocolMap(e.Orm)
		for i := range list {
			if len(list[i].DeptId) > 0 {
				if deptName, ok := groupIdName[list[i].DeptId]; ok {
					list[i].DeptName = deptName
				}
			}
			if timeI, ok := constant.DevOnlineCache.Get(list[i].Sn); ok {
				list[i].OnlineStatus = enums.Online
				timeTime, _ := time.ParseInLocation("2006-01-02 15:04:05", timeI, time.Local)
				t := cDto.LocalTime(timeTime)
				list[i].LastLiveTime = &t
			} else {
				list[i].OnlineStatus = enums.Offline
			}
			if len(list[i].Protocol) > 0 {
				if sysprotocol, ok := protocolMap[list[i].Protocol]; ok {
					list[i].ProtocolName = sysprotocol.Label
				}
			}
		}
		result = list
	}
	return
}

func (e *SysDev) FindDevice(req *query.SysDevQuery) (err error, one SysDev) {
	err = e.FindOne(&one, cDto.MakeCondition(req.GetNeedSearch()))
	if len(one.Id) > 0 {
		if len(one.DeptId) > 0 {
			groupIdName, _ := utils.GroupIdName(e.Orm, []string{one.DeptId})
			if deptName, ok := groupIdName[one.DeptId]; ok {
				one.DeptName = deptName
			}
		}
		if len(one.Protocol) > 0 {
			protocolMap := (&SysProtocol{}).FindProtocolMap(e.Orm, one.Protocol)
			if len(protocolMap) > 0 {
				one.ProtocolName = protocolMap[one.Protocol].Label
			}
		}
		if timeI, ok := constant.DevOnlineCache.Get(one.Sn); ok {
			one.OnlineStatus = enums.Online
			timeTime, _ := time.ParseInLocation("2006-01-02 15:04:05", timeI, time.Local)
			t := cDto.LocalTime(timeTime)
			one.LastLiveTime = &t
		} else {
			one.OnlineStatus = enums.Offline
		}
	}
	return
}

func (e *SysDev) AddDevice(addDev *SysDev, fromLoginInit ...bool) (err error) {
	if len(fromLoginInit) == 0 || !fromLoginInit[0] {
		tmpDev := SysDev{}
		cond := cDto.MakeCondition(query.SysDevQuery{IdSn: addDev.Sn})
		if err = e.FindOne(&tmpDev, cond); err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return
		}
		if len(tmpDev.Id) > 0 {
			// sn存在
			if tmpDev.DeptId != constant.AnonymousGroupId {
				err = errors.New("无法添加重复的设备SN")
				return
			}
			// 是陌生设备，则先删后加(不更新，为了逻辑走全)
			if err = e.Delete(&tmpDev, cond); err != nil {
				return
			}
			// 保留陌生设备之前的部分状态
			if timeI, ok := constant.DevOnlineCache.Get(tmpDev.Sn); ok {
				addDev.OnlineStatus = enums.Online
				timeTime, _ := time.ParseInLocation("2006-01-02 15:04:05", timeI, time.Local)
				t := cDto.LocalTime(timeTime)
				addDev.LastLiveTime = &t
			} else {
				addDev.OnlineStatus = enums.Offline
				addDev.LastLiveTime = tmpDev.LastLiveTime
			}
		}
	}
	err = e.Add(addDev)
	return
}

func (e *SysDev) UpdateOnlineStatus(devSn, to, now, status string) error {
	modMap := map[string]interface{}{
		"online_status": status,
	}
	// 更新表
	flag := false
	devQuery := query.SysDevQuery{}
	if status == enums.Online {
		// 上线
		if constant.DevOnlineCache.Has(devSn) {
			// 状态未变
		} else {
			// 状态改变
			if len(to) > 0 && to != devSn {
				// 设备
				devQuery.SnIn = []string{devSn, to}
			} else {
				// 网关
				devQuery.Sn = devSn
			}
			// 末次心跳
			modMap["last_live_time"] = *utils.FormatStrTimePoint(now)
			flag = true
		}
		// 更新末次心跳缓存
		if len(to) > 0 && to != devSn {
			constant.DevOnlineCache.Set(to, now)
			constant.StatusSnCache.Set(to, to)
		}
		constant.DevOnlineCache.Set(devSn, now)
		constant.StatusSnCache.Set(devSn, devSn)
	} else if status == enums.Offline {
		// 下线
		if len(to) > 0 && to != devSn {
			// 设备
			devQuery.Sn = to
			constant.DevOnlineCache.Remove(to)
			constant.StatusSnCache.Remove(to)
		} else {
			// 网关
			devQuery.GateOrSn = devSn
			constant.DevOnlineCache.Remove(devSn)
			constant.StatusSnCache.Remove(devSn)
			if devs, ok := constant.GateDevsCache.Get(devSn); ok {
				for _, dsn := range devs {
					constant.DevOnlineCache.Remove(dsn)
					constant.StatusSnCache.Remove(dsn)
				}
			}
		}
		// 在线的才更新下线
		devQuery.OnlineStatus = enums.Online
		// 登出时间
		modMap["logout_time"] = *utils.FormatStrTimePoint(now)
		flag = true
	} else {
		return nil
	}
	if !flag {
		return nil
	}
	return e.ModMap(e, modMap, cDto.MakeCondition(devQuery))
}

func (e *SysDev) ExistDevice(idSn string) (bool, error) {
	var count int64
	if err := e.FindCount(e, &count, cDto.MakeCondition(query.SysDevQuery{IdSn: idSn})); err != nil {
		return false, err
	}
	return count > 0, nil
}

func (e *SysDev) FindDeviceBySn(sn string) (dev SysDev, err error) {
	if err = e.FindOne(&dev, cDto.MakeCondition(query.SysDevQuery{Sn: sn})); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			err = nil
			return
		}
	}
	return
}

func (e *SysDev) RemoveDevBySn(sn string) (err error) {
	if err = e.Orm.Transaction(func(tx *gorm.DB) error {
		// 事务orm
		e.Orm = tx
		devConfigModel := &SysDevConfig{}
		devSensorModel := &SysDevSensor{}
		devSystemModel := &SysDevSystem{}
		devConfigModel.Orm = tx
		devSensorModel.Orm = tx
		devSystemModel.Orm = tx
		// 设备基本信息
		if err = e.Delete(e, cDto.MakeCondition(query.SysDevQuery{Sn: sn})); err != nil {
			return err
		}
		// 设备profile.config
		if err = devConfigModel.Delete(devConfigModel, cDto.MakeCondition(query.SysDevConfigQuery{Sn: sn})); err != nil {
			return err
		}
		// 设备profile.sensor
		if err = devSensorModel.Delete(devSensorModel, cDto.MakeCondition(query.SysDevSensorQuery{Sn: sn})); err != nil {
			return err
		}
		// 设备profile.system
		if err = devSystemModel.Delete(devSystemModel, cDto.MakeCondition(query.SysDevSystemQuery{Sn: sn})); err != nil {
			return err
		}
		// 删分组/设备缓存
		_ = env.Cache.HashDel(constant.DeviceDeptCache, sn)
		_ = env.Cache.HashDel(constant.DeviceEasyCache, sn)
		constant.SensorCache.Remove(sn)
		constant.DevOnlineCache.Remove(sn)
		constant.StatusSnCache.Remove(sn)
		constant.DevGateCache.Remove(sn)
		constant.AutoLoginSn.Delete(sn)
		constant.GateModelCache.Remove(sn)
		if devs, ok := constant.GateDevsCache.Get(sn); ok {
			constant.GateDevsCache.Remove(sn)
			for _, devSn := range devs {
				constant.DevOnlineCache.Remove(devSn)
				constant.StatusSnCache.Remove(devSn)
			}
		} else {
			for tp := range constant.GateDevsCache.IterBuffered() {
				gateSn := tp.Key
				devSns := tp.Val
				if utils.StrIn(sn, devSns) {
					if devsLen := ameda.StringsRemoveFirst(&devSns, sn); devsLen == 0 {
						constant.GateDevsCache.Remove(gateSn)
					} else {
						constant.GateDevsCache.Set(gateSn, devSns)
					}
				}
			}
		}
		for tp := range constant.SensorSnKey.IterBuffered() {
			key := tp.Key
			if strings.HasPrefix(key, sn+"#") {
				constant.SensorSnKey.Remove(key)
			}
		}
		// 广播消息
		mqtt.MqttClient.Publish(constant.DeviceDeleteMsgTopic, utils.MapToBytes(map[string]interface{}{"sn": sn}))
		return nil
	}); err != nil {
		return
	}
	return
}

func (e *SysDev) GetDevSnList(cond ...func(db *gorm.DB) *gorm.DB) (snList []string, snDevMap map[string]SysDev) {
	snList = make([]string, 0)
	snDevMap = make(map[string]SysDev)
	devList := make([]SysDev, 0)
	if err := e.FindList(&devList, cond...); err != nil {
		return
	}
	for i, dev := range devList {
		snList = append(snList, dev.Sn)
		snDevMap[dev.Sn] = devList[i]
	}
	return
}

func (e *SysDev) FindStatCount(req *query.SysDevQuery) (err error, result interface{}) {
	list := make([]SysDev, 0)
	if err = e.FindList(&list, cDto.MakeCondition(req.GetNeedSearch())); err != nil {
		return
	}
	if req.ByProduct {
		pdModel := SysDevProduct{}
		pdModel.Orm = e.Orm
		pds := make([]SysDevProduct, 0)
		if err = pdModel.FindList(&pds, cDto.MakeCondition(query.SysProductQuery{DeptIdIn: req.DeptIdIn})); err != nil {
			return
		}
		byProduct := make(map[string]map[string]int)
		for _, v := range pds {
			byProduct[v.ProductModel] = make(map[string]int)
			byProduct[v.ProductModel][enums.Online] = 0
			byProduct[v.ProductModel][enums.Offline] = 0
		}
		arr := make([]map[string]interface{}, 0)
		for _, v := range list {
			if len(v.ProductModel) == 0 {
				continue
			}
			if _, ok := byProduct[v.ProductModel]; !ok {
				continue
			}
			if constant.DevOnlineCache.Has(v.Sn) {
				byProduct[v.ProductModel][enums.Online]++
			} else {
				byProduct[v.ProductModel][enums.Offline]++
			}
		}
		modelName := pdModel.GetModelName()
		for productModel, countMap := range byProduct {
			tmp := map[string]interface{}{
				"productModel": productModel,
				"productName":  modelName[productModel],
			}
			for k, v := range countMap {
				tmp[k] = v
			}
			arr = append(arr, tmp)
		}
		result = arr
	} else {
		statCount := make(map[string]int)
		statCount[enums.Online] = 0
		statCount[enums.Offline] = 0
		for _, v := range list {
			if constant.DevOnlineCache.Has(v.Sn) {
				statCount[enums.Online]++
			} else {
				statCount[enums.Offline]++
			}
		}
		result = statCount
	}
	return
}
